/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.xeerkat.xmpp;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import org.infoset.xml.Element;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentSource;
import org.infoset.xml.util.XMLWriter;

/**
 *
 * @author alex
 */
public class MultiUserChat {

   static Logger LOG = Logger.getLogger(MultiUserChat.class.getName());
   
   public static final String FEATURE = "http://jabber.org/protocol/muc";
   ClientConnection connection;
   StanzaRouter router;
   ID identity;
   ID room;
   boolean inRoom;
   boolean isConfigured;

   String role;
   String affiliation;

   Name error;

   StanzaRouter.Route messageRoute;
   StanzaRouter.Route presenceRoute;
   ItemDestination messageDestination;

   public MultiUserChat(ClientConnection connection,StanzaRouter router) {
      this.connection = connection;
      this.router = router;
      this.room = null;
      this.inRoom = false;
      this.isConfigured = false;
   }

   public ID getRoom() {
      return this.room;
   }

   public String getRole() {
      return this.role;
   }

   public String getAffiliation() {
      return this.affiliation;
   }
   
   public void setMessageDestination(ItemDestination dest) {
      this.messageDestination = dest;
   }

   public boolean isInRoom() {
      return inRoom;
   }

   public boolean isConfigured() {
      return isConfigured;
   }

   public Name getLastError() {
      return error;
   }

   public void joinRoom(String name,String domain,String ownerNickname)
      throws XMLException
   {
      joinRoom(name,domain,ownerNickname,null);
   }

   public void joinRoom(String name,String domain,String ownerNickname,List<Element> additionalInfo)
      throws XMLException
   {
      name = name.toLowerCase();
      domain = domain.toLowerCase();
      this.identity = new ID(name,domain,ownerNickname);
      this.room = new ID(name,domain);
      this.inRoom = false;

      final String roomFrom = this.room.toString();

      this.messageRoute = router.addRoute(
         new StanzaRouter.RouteTest() {
            public boolean matches(Element start) {
               return start.getName().equals(Protocol.MESSAGE) && "groupchat".equals(start.getAttributeValue("type")) && start.getAttributeValue("from").startsWith(roomFrom);
            }
         },messageDestination);

      final AtomicReference<StanzaRouter.Route> refCreateRoute = new AtomicReference<StanzaRouter.Route>();
      this.presenceRoute = router.addRoute(
         new StanzaRouter.RouteTest() {
            public boolean matches(Element start) {
               return start.getName().equals(Protocol.PRESENCE) && start.getAttributeValue("from").startsWith(roomFrom);
            }
         },
         new StanzaDestination() {
            public void onStanza() {
               StringWriter w = new StringWriter();
               try {
                  XMLWriter.writeElement(getStanza(), w);
               } catch (Exception ex) {}
               LOG.info("MUC: "+w.toString());
               Element presence = getStanza();
               if (inRoom) {
                  if (identity.toString().equals(presence.getAttributeValue("from"))) {
                     if ("unavailable".equals(presence.getAttributeValue("type"))) {
                        inRoom = false;
                        detach();
                        onLeave();
                     }
                  } else {
                     // handle occupant messages
                     LOG.info(presence.getAttributeValue("from")+" "+presence.getAttributeValue("type"));
                  }
               } else {
                  // not in room, must have just joined
                  
                  // check for errors
                  if ("error".equals(presence.getAttributeValue("type"))) {
                     Element errorChild = presence.getFirstElementNamed(Protocol.ERROR);
                     if (errorChild!=null) {
                        Element errorType = errorChild.getElementChildren().next();
                        error = errorType==null ? null : errorType.getName();
                     } else {
                        LOG.severe("Cannot join room due to error but no error was given.");
                     }
                  } else if (identity.toString().equals(presence.getAttributeValue("from"))){
                     String type = presence.getAttributeValue("type");
                     if (type==null || "available".equals(type)) {
                        Element mucUserX = presence.getFirstElementNamed(Protocol.MUC_USER_X);
                        if (mucUserX==null) {
                           LOG.severe("Missing MUC user response from server.");
                           return;
                        }
                        Element item = mucUserX.getFirstElementNamed(Protocol.MUC_USER_ITEM);
                        role = item.getAttributeValue("role");
                        affiliation = item.getAttributeValue("affiliation");
                        Iterator<Element> statusCodes = mucUserX.getElementsByName(Protocol.MUC_USER_STATUS);
                        boolean created = false;
                        while (statusCodes.hasNext()) {
                           String codeS = statusCodes.next().getAttributeValue("code");
                           LOG.info("code: "+codeS);
                           if (codeS!=null) {
                              if (Integer.parseInt(codeS)==110) {
                                 inRoom = true;
                              }
                              if (Integer.parseInt(codeS)==201) {
                                 created = true;
                                 inRoom = true;
                              }
                           }
                        }
                        if (created && "owner".equals(affiliation)) {
                           onCreateRoom();
                        }
                        if (inRoom) {
                           onJoin();
                        }
                     }
                  } else {
                     LOG.severe("Unsure of what to do with presence from "+presence.getAttributeValue("from"));
                  }
               }
            }
         }
      );

      ItemDestination stanza = connection.getStanzaDestination();
      ItemConstructor constructor = connection.getItemConstructor();
      Element presence = constructor.createElement(Protocol.PRESENCE);
      presence.setAttributeValue("to", identity.toString());
      //presence.setAttributeValue("from",connection.getBindID().toString());
      stanza.send(presence);
      stanza.send(constructor.createElement(Protocol.MUC_X));
      stanza.send(constructor.createElementEnd(Protocol.MUC_X));
      if (additionalInfo!=null) {
         for (Element info : additionalInfo) {
            DocumentSource.generate(info, false, stanza);
         }
      }
      stanza.send(constructor.createElementEnd(Protocol.PRESENCE));
   }

   public void sendMessage(String message)
      throws XMLException
   {
      sendMessage(message,null);
   }
   
   public void sendMessage(String message,Element data)
      throws XMLException
   {
      ItemDestination dest = connection.getStanzaDestination();
      ItemConstructor constructor = connection.getItemConstructor();
      Element messageE = constructor.createElement(Protocol.MESSAGE);
      //messageE.setAttributeValue("from",connection.getBindID().toString());
      messageE.setAttributeValue("to",this.room.toString());
      messageE.setAttributeValue("type","groupchat");
      dest.send(messageE);
      dest.send(constructor.createElement(Protocol.BODY));
      dest.send(constructor.createCharacters(message));
      dest.send(constructor.createElementEnd(Protocol.BODY));
      DocumentSource.generate(data, false, dest);
      dest.send(constructor.createElementEnd(Protocol.MESSAGE));
   }

   public void leave()
      throws XMLException
   {
      ItemDestination stanza = connection.getStanzaDestination();
      ItemConstructor constructor = connection.getItemConstructor();
      Element presence = constructor.createElement(Protocol.PRESENCE);
      presence.setAttributeValue("to", identity.toString());
      //presence.setAttributeValue("from",connection.getBindID().toString());
      presence.setAttributeValue("type","unavailable");
      stanza.send(presence);
      stanza.send(constructor.createElementEnd(Protocol.PRESENCE));
   }

   public void detach() {
      this.router.removeRoute(messageRoute);
      this.messageRoute = null;
      this.router.removeRoute(presenceRoute);
      this.presenceRoute = null;
   }

   public void onJoin() {
      
   }

   public void onLeave() {

   }

   public void onCreateRoom() {

   }

   public void onRoomConfiguration(Map<String,FormField> configuration) {
      
   }

   public void onRoomConfigured() {

   }

   public void getRoomConfiguration() 
      throws XMLException
   {
      final String id = UUID.randomUUID().toString();
      final AtomicReference<StanzaRouter.Route> refRoute = new AtomicReference<StanzaRouter.Route>();
      refRoute.set(
         router.addRouteByAttribute("id",id,StanzaRouter.ComparisonType.Equals, new StanzaDestination() {
            public void onStanza() {
               StringWriter w = new StringWriter();
               try {
                  XMLWriter.writeElement(getStanza(), w);
               } catch (Exception ex) {}
               LOG.info(w.toString());
               Element ownerQuery = getStanza().getFirstElementNamed(Protocol.MUC_OWNER_QUERY);
               if (ownerQuery==null) {
                  onRoomConfiguration(null);
                  return;
               }
               Element x = ownerQuery.getFirstElementNamed(Protocol.DATA_X);
               if (x==null) {
                  onRoomConfiguration(null);
                  return;
               }
               Map<String,FormField> configuration = new TreeMap<String,FormField>();
               Iterator<Element> fields = x.getElementsByName(Protocol.DATA_FIELD);
               while (fields.hasNext()) {
                  Element fieldE = fields.next();
                  String var = fieldE.getAttributeValue("var");
                  String type = fieldE.getAttributeValue("type");
                  String label = fieldE.getAttributeValue("label");
                  Element value = fieldE.getFirstElementNamed(Protocol.DATA_VALUE);
                  FormField field = new FormField(var);
                  field.setLabel(label);
                  field.setType(type);
                  field.setValue(value==null ? null : value.getText());
                  if (type!=null && "list-single".equals(type.trim())) {
                     List<FormOption> options = new ArrayList<FormOption>();
                     Iterator<Element> optionValues = fieldE.getElementsByName(Protocol.DATA_OPTION);
                     while (optionValues.hasNext()) {
                        Element option = optionValues.next();
                        Element optionValue = option.getFirstElementNamed(Protocol.DATA_VALUE);
                        if (optionValue!=null) {
                           options.add(new FormOption(option.getAttributeValue("label"),optionValue.getText()));
                        }
                     }
                     field.setOptions(options);
                  }
                  configuration.put(var, field);
               }
               onRoomConfiguration(configuration);
            }
         })
      );
      ItemDestination stanza = connection.getStanzaDestination();
      ItemConstructor constructor = connection.getItemConstructor();
      Element iq = constructor.createElement(Protocol.IQ);
      iq.setAttributeValue("to", room.toString());
      //iq.setAttributeValue("from",connection.getBindID().toString());
      iq.setAttributeValue("type","get");
      iq.setAttributeValue("id",id);
      stanza.send(iq);
      stanza.send(constructor.createElement(Protocol.MUC_OWNER_QUERY));
      stanza.send(constructor.createElementEnd(Protocol.MUC_OWNER_QUERY));
      stanza.send(constructor.createElementEnd(Protocol.IQ));
   }

   public void configureRoom(List<FormField> values)
      throws XMLException
   {
      final String id = UUID.randomUUID().toString();
      final AtomicReference<StanzaRouter.Route> refRoute = new AtomicReference<StanzaRouter.Route>();
      refRoute.set(
         router.addRouteByAttribute("id",id,StanzaRouter.ComparisonType.Equals, new StanzaDestination() {
            public void onStanza() {
               router.removeRoute(refRoute.get());
               if ("error".equals(getStanza().getAttributeValue("type"))) {
                  Element errorE = getStanza().getFirstElementNamed(Protocol.ERROR);
                  boolean logError = true;
                  if (errorE!=null) {
                     String code = errorE.getAttributeValue("code");
                     if (code!=null && code.trim().equals("405")) {
                        // instant room cannot be created, try getting configuration
                        Iterator<Element> errorTypes = errorE.getElementChildren();
                        error = errorTypes.hasNext() ? errorTypes.next().getName() : null;
                        isConfigured = false;
                     }
                  }
                  /*
                  StringWriter w = new StringWriter();
                  try {
                     XMLWriter.writeElement(getStanza(), w);
                  } catch (Exception ex) {}
                  LOG.info("configure room reponse: "+w.toString());
                   */
                  onRoomConfigured();
               } else if ("result".equals(getStanza().getAttributeValue("type"))) {
                  isConfigured = true;
                  onRoomConfigured();
               } else {
                  StringWriter w = new StringWriter();
                  try {
                     XMLWriter.writeElement(getStanza(), w);
                  } catch (Exception ex) {}
                  LOG.severe("Unrecognized configure room reponse: "+w.toString());
               }
            }
         }));
      ItemDestination stanza = connection.getStanzaDestination();
      ItemConstructor constructor = connection.getItemConstructor();
      Element iq = constructor.createElement(Protocol.IQ);
      iq.setAttributeValue("to", room.toString());
      //iq.setAttributeValue("from",connection.getBindID().toString());
      iq.setAttributeValue("type","set");
      iq.setAttributeValue("id",id);
      stanza.send(iq);
      Element query = constructor.createElement(Protocol.MUC_OWNER_QUERY);
      query.addNamespaceBinding(Name.NO_PREFIX, Protocol.XMPP_MUC_OWNER);
      stanza.send(query);
      Element x = constructor.createElement(Protocol.DATA_X);
      x.addNamespaceBinding(Name.NO_PREFIX, Protocol.XMPP_DATA);
      x.setAttributeValue("type","submit");
      stanza.send(x);
      if (values!=null) {
         for (FormField field : values) {
            Element fieldE = constructor.createElement(Protocol.DATA_FIELD);
            fieldE.setAttributeValue("var",field.getVar());
            stanza.send(fieldE);
            stanza.send(constructor.createElement(Protocol.DATA_VALUE));
            if (field.getValue()!=null && field.getValue().length()>0) {
               stanza.send(constructor.createCharacters(field.getValue()));
            }
            stanza.send(constructor.createElementEnd(Protocol.DATA_VALUE));
            stanza.send(constructor.createElementEnd(Protocol.DATA_FIELD));

         }
      }
      stanza.send(constructor.createElementEnd(Protocol.DATA_X));
      stanza.send(constructor.createElementEnd(Protocol.MUC_OWNER_QUERY));
      stanza.send(constructor.createElementEnd(Protocol.IQ));

   }
}
